using System;
using Microsoft.SharePoint;

namespace BilSimser.SharePoint.Common.Data
{
	/// <summary>
	/// The ListProvider is a data access class for accessing 
	/// SharePoint lists. It retrieves list data through additional
	/// classes like <see cref="ListItem"/> and <see cref="ListDescriptor"/>
	/// </summary>
	public class ListProvider
	{
		private SPWeb web = null;

		/// <summary>
		/// Initializes a new instance of the <see cref="ListProvider"/> class.
		/// </summary>
		/// <param name="web">The web.</param>
		public ListProvider (SPWeb web)
		{
			this.web = web;
		}

		#region Public Methods

		/// <summary>
		/// Gets the list item by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldName">Name of the field.</param>
		/// <param name="fieldValue">The field value.</param>
		/// <returns></returns>
		public ListItem GetListItemByField (string listName, string fieldName, string fieldValue)
		{
			SPList spList = GetList(listName);
			SPQuery spQuery = new SPQuery();
			spQuery.Query = "<Where>" + GetCAMLClause(spList, fieldName, fieldValue) + "</Where>";

			SPListItemCollection matchingItems = spList.GetItems(spQuery);

			if (matchingItems.Count > 0)
				return SPListItemtoDescriptor(matchingItems[0]);
			else
				return null;
		}

		/// <summary>
		/// Gets the list item by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldNames">The field names.</param>
		/// <param name="fieldValues">The field values.</param>
		/// <returns></returns>
		public ListItem GetListItemByField (string listName, string[] fieldNames, string[] fieldValues)
		{
			SPList spList = GetList(listName);
			string criteria = "";

			if (fieldNames.Length != fieldValues.Length) throw new Exception("# of values must match # of fields");
			for (int i = 0; i < fieldNames.Length; i++)
			{
				string clause = GetCAMLClause(spList, fieldNames[i], fieldValues[i]);

				if (criteria.Length > 0)
					criteria = "<And>" + criteria + clause + "</And>";
				else
					criteria = clause;
			}

			SPQuery spQuery = new SPQuery();
			spQuery.Query = "<Where>" + criteria + "</Where>";

			SPListItemCollection matchingItems = spList.GetItems(spQuery);

			if (matchingItems.Count > 0)
				return SPListItemtoDescriptor(matchingItems[0]);
			else
				return null;
		}

		/// <summary>
		/// Gets the list items by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldNames">The field names.</param>
		/// <param name="fieldValues">The field values.</param>
		/// <returns></returns>
		public ListDescriptor GetListItemsByField (string listName, string fieldNames, string fieldValues)
		{
			SPList spList = GetList(listName);
			SPQuery spQuery = new SPQuery();
			spQuery.Query = "<Where>" + GetCAMLClause(spList, fieldNames, fieldValues) + "</Where>";

			SPListItemCollection matchingItems = spList.GetItems(spQuery);

			ListDescriptor listDescriptor = new ListDescriptor();
			listDescriptor.ListId = spList.ID.ToString();
			listDescriptor.ListItems = new ListItem[matchingItems.Count];
			listDescriptor.ListName = spList.Title;

			for (int i = 0; i < matchingItems.Count; i++)
				listDescriptor.ListItems[i] = SPListItemtoDescriptor(matchingItems[i]);

			return listDescriptor;
		}

		/// <summary>
		/// Gets the list items by field.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="fieldNames">The field names.</param>
		/// <param name="fieldValues">The field values.</param>
		/// <returns></returns>
		public ListDescriptor GetListItemsByField (string listName, string[] fieldNames, string[] fieldValues)
		{
			SPList spList = GetList(listName);
			string criteria = "";

			if (fieldNames.Length != fieldValues.Length) throw new Exception("# of values must match # of fields");
			for (int i = 0; i < fieldNames.Length; i++)
			{
				string clause = GetCAMLClause(spList, fieldNames[i], fieldValues[i]);

				if (criteria.Length > 0)
					criteria = "<And>" + criteria + clause + "</And>";
				else
					criteria = clause;
			}

			SPQuery spQuery = new SPQuery();
			spQuery.Query = "<Where>" + criteria + "</Where>";

			SPListItemCollection matchingItems = spList.GetItems(spQuery);

			ListDescriptor listDescriptor = new ListDescriptor();
			listDescriptor.ListId = spList.ID.ToString();
			listDescriptor.ListItems = new ListItem[matchingItems.Count];
			listDescriptor.ListName = spList.Title;

			for (int i = 0; i < matchingItems.Count; i++)
				listDescriptor.ListItems[i] = SPListItemtoDescriptor(matchingItems[i]);

			return listDescriptor;
		}

		/// <summary>
		/// Gets the list itemby ID.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="itemID">The item ID.</param>
		/// <returns></returns>
		public ListItem GetListItembyID (string listName, int itemID)
		{
			SPList spList = GetList(listName);
			SPListItem spListItem = spList.Items.GetItemById(itemID);
			return SPListItemtoDescriptor(spListItem);
		}

		/// <summary>
		/// Gets all list items.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <returns></returns>
		public ListDescriptor GetAllListItems (string listName)
		{
			try
			{
				SPList spList = GetList(listName);

				ListDescriptor listDescriptor = new ListDescriptor();
				listDescriptor.ListId = spList.ID.ToString();
				listDescriptor.ListName = spList.Title;
				listDescriptor.ListItems = new ListItem[spList.ItemCount];
				for (int i = 0; i < spList.ItemCount; i++)
					listDescriptor.ListItems[i] = SPListItemtoDescriptor(spList.Items[i]);

				return listDescriptor;
			}
			catch (Exception)
			{
				return new ListDescriptor();
			}
		}

		/// <summary>
		/// Adds the list item.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="newItem">The new item.</param>
		/// <returns></returns>
		public int AddListItem (string listName, ListItem newItem)
		{
			SPList spList = GetList(listName);
			spList.ParentWeb.AllowUnsafeUpdates = true;
			SPListItem spListItem = spList.Items.Add();

			UpdateSPListItem(spListItem, newItem);
			spListItem.Update();

			return spListItem.ID;
		}

		/// <summary>
		/// Deletes the list item.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="itemID">The item ID.</param>
		public void DeleteListItem (string listName, int itemID)
		{
			web.Lists.IncludeRootFolder = true;
			SPList spList = web.Lists[listName];
			spList.Items.DeleteItemById(itemID);
			spList.Update();
		}

		/// <summary>
		/// Gets a collection of all the lists from a web site.
		/// </summary>
		/// <returns></returns>
		public ListDescriptor[] GetLists()
		{
			web.Lists.IncludeRootFolder = true;

			ListDescriptor[] listDescriptors = new ListDescriptor[web.Lists.Count];
			for (int i = 0; i < listDescriptors.Length; i++)
			{
				listDescriptors[i] = new ListDescriptor();
				listDescriptors[i].ListId = web.Lists[i].ID.ToString();
				listDescriptors[i].ListName = web.Lists[i].Title;
				listDescriptors[i].ListItems = null;
			}

			return listDescriptors;
		}

		/// <summary>
		/// Updates the list item.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <param name="updateItem">The update item.</param>
		/// <returns></returns>
		public int UpdateListItem (string listName, ListItem updateItem)
		{
			SPList spList = GetList(listName);
			spList.ParentWeb.AllowUnsafeUpdates = true;
			SPListItem spListItem = spList.Items.GetItemById(updateItem.Id);

			foreach (Field field in updateItem.Fields)
			{
				try
				{
					if (!spListItem.Fields.GetField(field.Name).ReadOnlyField)
						if ((spListItem[field.Name] == null) || (spListItem[field.Name].ToString() != field.Value))
						{
							spListItem[field.Name] = field.Value;
						}
				}
				catch (Exception)
				{
					// ignore field-level errors
				}
			}
			spListItem.Update();

			return spListItem.ID;
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Gets the CAML clause.
		/// </summary>
		/// <param name="spList">The sp list.</param>
		/// <param name="fieldName">Name of the field.</param>
		/// <param name="fieldValue">The field value.</param>
		/// <returns></returns>
		private string GetCAMLClause (SPList spList, string fieldName, string fieldValue)
		{
			string dataType = spList.Fields.GetField(fieldName).TypeAsString;
			string queryType = "Eq";

			if (dataType == "DateTime")
				fieldValue = DateTime.Parse(fieldValue).ToString("yyyy-MM-ddTHH:mm:ssZ");
			else
			{
				// Determine if this should be BeginsWith, Contains, or Eq
				// based on position of "*" in the query. This will
				// provide us with like matching
				int wildcardIndex = fieldValue.IndexOf("*");
				if (wildcardIndex == 0)
				{
					queryType = "Contains";
					fieldValue = fieldValue.Remove(wildcardIndex, 1);
				}
				else if (wildcardIndex == (fieldValue.Length - 1))
				{
					queryType = "BeginsWith";
					fieldValue = fieldValue.Remove(wildcardIndex, 1);
				}
			}

			return string.Format(
				"<{0}><FieldRef Name=\"{1}\" /><Value Type=\"{2}\">{3}</Value></{0}>",
				queryType,
				spList.Fields.GetField(fieldName).InternalName,
				dataType,
				fieldValue.ToUpper()
				);
		}

		/// <summary>
		/// Gets the list.
		/// </summary>
		/// <param name="listName">Name of the list.</param>
		/// <returns></returns>
		private SPList GetList (string listName)
		{
			SPList list;
			web.Lists.IncludeRootFolder = true;

			if ((listName.Trim().Length == 36) && (new Guid(listName).ToString() == listName))
			{
				list = web.Lists[new Guid(listName)];
			}
			else
			{
				list = web.Lists[listName];
			}

			return list;
		}

		/// <summary>
		/// Converts a <see cref="SPListItem"/> to a <see cref="ListItem"/> object.
		/// </summary>
		/// <param name="spListItem">The SPList item to convert.</param>
		/// <returns></returns>
		private ListItem SPListItemtoDescriptor (SPListItem spListItem)
		{
			ListItem listItem = new ListItem();
			string fieldName;
			for (int j = 0; j < spListItem.Fields.Count; j++)
				if (!spListItem.Fields[j].Hidden)
					try
					{
						listItem.Id = spListItem.ID;
						fieldName = spListItem.Fields[j].InternalName;
						string val = (spListItem[fieldName] != null ? spListItem[spListItem.Fields[j].InternalName].ToString() : "");
						listItem.Add(fieldName, val, spListItem.Fields[j].ReadOnlyField);
					}
					catch (Exception)
					{
					}

			return listItem;
		}

		/// <summary>
		/// Updates the SP list item.
		/// </summary>
		/// <param name="spListItem">The sp list item.</param>
		/// <param name="listItem">The list item.</param>
		private void UpdateSPListItem (SPListItem spListItem, ListItem listItem)
		{
			foreach (Field field in listItem.Fields)
			{
				try
				{
					switch (spListItem.Fields.GetField(field.Name).Type)
					{
						case SPFieldType.DateTime:
							spListItem[field.Name] = DateTime.Parse(field.Value);
							break;

						default:
							spListItem[field.Name] = field.Value;
							break;
					}
				}
				catch (Exception)
				{
				}
			}
		}

		#endregion
	}
}